#include "./includes/utility.h"

int main (int argc, const char * argv[]) {
    int listener,fdmax;
	int nbytes,bytes_rcv,enc_bytes;
	int new_cl; 									//Socket for the new client
	struct client* new_client;
	struct client* ptr_cl;
	void *buf;
	struct sockaddr_in my_addr,cl_addr;
	socklen_t addrlen=sizeof(cl_addr);
	fd_set master;
	fd_set read_fds;
	int yes = 1;
	char cmd[4];
	struct client* list_client = NULL;				//Connected client list
	struct reg_client* r_client;					//registered client list
	char* server_dir = "./server_file_directory/";
	char* client_list_file = "./client_list";

	void* datareceived;
	char *file_pem_priv;
	char *file_pem_pub;
	FILE *fp;
	RSA *rsa;
	int bits;
	unsigned long exp;
	unsigned char *kstr;

	int bsize,pt_len,ct_len,n_dt;

	if (argc != 3)
		error("Parameters Error: Server IP and PORT needed");
	TCP_init(&listener,&my_addr,argv[1],atoi(argv[2]));
	if(setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == - 1)
		error("Server-setsockopt() error!");
	TCP_bind(listener,&my_addr);
	printf("Server: Online (%s:%d)\n",inet_ntoa(my_addr.sin_addr),ntohs(my_addr.sin_port));

	FD_ZERO(&master);
	FD_SET(listener, &master);
	fdmax = listener;

	file_pem_priv = "priv.pem";
	file_pem_pub = "pub.pem";
	bits = 1024;
	exp = RSA_F4;

	//To generate key pair dynamically
	/*
	OpenSSL_add_all_algorithms();
	rsa = RSA_generate_key(bits, exp, NULL, NULL);
	fp = fopen(file_pem_priv, "w");
	kstr = "1234";
	PEM_write_RSAPrivateKey(fp, rsa, EVP_des_ede3_cbc(), kstr, strlen(kstr), NULL, NULL);
	fclose(fp);
	fp = fopen(file_pem_pub, "w");
	PEM_write_RSAPublicKey(fp, rsa);
	fclose(fp);
	RSA_free(rsa);
	printf("Server: Key pair generated\n");
	*/
	
	r_client = generate_reg_client_list(client_list_file);
	printf("Server: Registered client list generated\n");

	for(;;) {
		int i;
		read_fds = master;
		if(select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1)
			error("Server-select() error");
		for(i = 0; i <= fdmax; i++) {
			if(FD_ISSET(i, &read_fds)){
				//New client
				if(i == listener) {
					 if((new_cl = accept(listener, (struct sockaddr *)&cl_addr, &addrlen)) == -1) {
						 perror("Server-accept() error");
					 }
					 else {
						//1st protocol step [(M1) : C -> S]
						unsigned char *plaintext,*ciphertext;

						FD_SET(new_cl, &master);
						if(new_cl > fdmax) {
						  fdmax = new_cl;
						}
						new_client=(struct client*) malloc(sizeof(struct client));
						memset(&(new_client->addr),0,sizeof((new_client->addr)));
						new_client->id_sck=new_cl;
						new_client->addr.sin_addr = cl_addr.sin_addr;//client address
						new_client->addr.sin_port = cl_addr.sin_port;//client port
						new_client->addr.sin_family=AF_INET;
						new_client->username[0]='\0';
						new_client->protocol_step = 0;
						ins_list(&list_client, new_client);
						//printf("[(M1) : C -> S]\n");
						printf("Server: Connection request by client %s:%d on socket %d\n", inet_ntoa(cl_addr.sin_addr),ntohs(cl_addr.sin_port), new_cl);
						fflush(stdout);
						create_nonce(new_client->server_nonce);
						OpenSSL_add_all_algorithms();
						rsa = RSA_new();
						fp = fopen(file_pem_priv, "r");
						PEM_read_RSAPrivateKey(fp, &rsa,NULL,NULL);
						fclose(fp);
						bsize = RSA_size(rsa);
						pt_len = strlen("!INIT")+NONCELEN;
						plaintext = (unsigned char*)malloc(pt_len);
						memcpy(plaintext,"!INIT",strlen("!INIT"));
						memcpy(&plaintext[strlen("!INIT")],new_client->server_nonce,NONCELEN);
						ct_len = bsize;
						ciphertext = (unsigned char*)malloc(bsize);
						enc_bytes = RSA_private_encrypt(pt_len,plaintext,ciphertext,rsa,RSA_PKCS1_PADDING);
						if(enc_bytes == -1){
							char* err;
							err = (char*)malloc(130);
							ERR_load_CRYPTO_strings();
							ERR_error_string(ERR_get_error(),err);
							printf("%s\n",err);
							perror("Error: ");
							free(err);
							RSA_free(rsa);
							break;
							//exit(1);
						}
						TCP_send(new_client->id_sck,(void*)&ct_len,sizeof(int));
						TCP_send(new_client->id_sck,(void*)ciphertext,ct_len);
						//printf("[(M2) : S -> C]\n");
						free(ciphertext);
						free(plaintext);
						RSA_free(rsa);
					 } 
				}
				//message from clients already connected
				else {
					//search client descriptor
					ptr_cl=checkID_list(list_client, i);
					if (ptr_cl == NULL) {
						printf("Error: Client not connected yet\n");
						continue;
					}
					switch(ptr_cl->protocol_step){
						//receive the shared key, nonce, username and password
						case 0:
						{
							unsigned char *ciphertext,*cipherkey,*tmp,*buffer;
							struct reg_client* temp;
							char password[PSSWLEN];
							char* resp_msg;
							int resp_len;

							if(TCP_recv(i, (void *) &nbytes, sizeof(int)) <= 0){
								printf("Server: client on socket %d disconnected\n",ptr_cl->id_sck);
								printf("Server: connection %s:%d closed on socket %d\n", inet_ntoa(ptr_cl->addr.sin_addr),ntohs(ptr_cl->addr.sin_port), ptr_cl->id_sck);
							  	FD_CLR(ptr_cl->id_sck, &master);
								if (rmID_list(&list_client,ptr_cl->id_sck,ptr_cl->username) == -1)
									error("Error: client remotion failed");
								break;
							}
							datareceived = (unsigned char*)malloc(nbytes);
							if(TCP_recv(i, (void *) datareceived, nbytes) <= 0){
								printf("Server: client on socket %d disconnected\n",ptr_cl->id_sck);
								printf("Server: connection %s:%d closed on socket %d\n", inet_ntoa(ptr_cl->addr.sin_addr),ntohs(ptr_cl->addr.sin_port), ptr_cl->id_sck);
								FD_CLR(ptr_cl->id_sck, &master);
								if (rmID_list(&list_client,ptr_cl->id_sck,ptr_cl->username) == -1)
									error("Error: client remotion failed");
								break;
							}
							//printf("[(M3) : C -> S]\n");
							//Decryption
							fflush(stdout);
							OpenSSL_add_all_algorithms();
							rsa = RSA_new();
							fp = fopen(file_pem_priv, "r");
							PEM_read_RSAPrivateKey(fp, &rsa,NULL,NULL);
							fclose(fp);
							bsize = RSA_size(rsa);
							cipherkey = (unsigned char*)malloc(bsize);
							buffer = (unsigned char*)malloc(bsize);
							memcpy(cipherkey,datareceived,bsize);
							if(RSA_private_decrypt(bsize,cipherkey,buffer,rsa,RSA_PKCS1_PADDING) == -1){
								char* err;
								err = (char*)malloc(130);
								ERR_load_CRYPTO_strings();
								ERR_error_string(ERR_get_error(),err);
								printf("%s\n",err);
								perror("Error: ");
								free(err);
								RSA_free(rsa);
								break;
								//exit(1);
							}
							RSA_free(rsa);
							memcpy(ptr_cl->sharedkey,buffer,EVP_MAX_KEY_LENGTH);
							//nonce verificarion
							if(memcmp(&buffer[EVP_MAX_KEY_LENGTH],ptr_cl->server_nonce,NONCELEN) != 0){
								//
								printf("Server: nonce not valid\n");
								TCP_send_enc_msg(ptr_cl->sharedkey,EVP_MAX_KEY_LENGTH, ptr_cl->id_sck, "!ER00", strlen("!ER00"));
								TCP_close(ptr_cl->id_sck);
								printf("Server: Connection %s:%d closed on socket %d\n", inet_ntoa(ptr_cl->addr.sin_addr),ntohs(ptr_cl->addr.sin_port), ptr_cl->id_sck);
								FD_CLR(ptr_cl->id_sck, &master);
								if (rmID_list(&list_client,ptr_cl->id_sck,ptr_cl->username) == -1)
									error("Error: client remotion failed");
								free(datareceived);
								free(cipherkey);
								free(buffer);
								break;
							}
							//Now we have the sharedkey
							//Symmetric Decryption for data encrypted with sharedkey
							ct_len = nbytes - bsize;
							ciphertext = (unsigned char*)malloc(ct_len);
							memcpy(ciphertext,&((unsigned char*)datareceived)[bsize],ct_len);
							//DECRYPT
							bsize = block_size();
							tmp = (unsigned char*)malloc(ct_len + bsize);
							pt_len = decrypt(ptr_cl->sharedkey,EVP_MAX_KEY_LENGTH,ciphertext,ct_len,tmp);
							//check if client is already connected
							char tmpusr[USERLEN];
							memcpy(tmpusr,tmp,USERLEN);
							if(checkUsername_list(list_client,tmpusr)){
								printf("Server: client already connected\n");
								TCP_send_enc_msg(ptr_cl->sharedkey,EVP_MAX_KEY_LENGTH, ptr_cl->id_sck, "!ER03", strlen("!ER03"));
								TCP_close(ptr_cl->id_sck);
								printf("Server: Connection %s:%d closed on socket %d\n", inet_ntoa(ptr_cl->addr.sin_addr),ntohs(ptr_cl->addr.sin_port), ptr_cl->id_sck);
								FD_CLR(ptr_cl->id_sck, &master);
								if (rmID_list(&list_client,ptr_cl->id_sck,ptr_cl->username) == -1)
									error("Error: client remotion failed");
								free(datareceived);
								free(buffer);
								free(ciphertext);
								free(tmp);
								free(cipherkey);
								break;
							}
							//copy username,password,nonce
							memcpy(ptr_cl->username,tmp,USERLEN);
							memcpy(password,&tmp[USERLEN],PSSWLEN);
							memcpy(ptr_cl->client_nonce,&tmp[USERLEN+PSSWLEN],NONCELEN);
							printf("Server: nonce OK for %s\n",ptr_cl->username);
							//check password between the two parts of the message
							if(memcmp(&buffer[EVP_MAX_KEY_LENGTH+NONCELEN],password,PSSWLEN) != 0){
								//
								printf("Server: password not valid\n");
								TCP_send_enc_msg(ptr_cl->sharedkey,EVP_MAX_KEY_LENGTH, ptr_cl->id_sck, "!ER02", strlen("!ER02"));
								TCP_close(ptr_cl->id_sck);
								printf("Server: Connection %s:%d closed on socket %d\n", inet_ntoa(ptr_cl->addr.sin_addr),ntohs(ptr_cl->addr.sin_port), ptr_cl->id_sck);
								FD_CLR(ptr_cl->id_sck, &master);
								if (remove_client(&list_client, ptr_cl->username) == -1)
									error("Error: client remotion failed");
								free(datareceived);
								free(cipherkey);
								free(buffer);
								break;
							}
							//check username and password
							if ((temp = checkUsr_list(r_client,ptr_cl->username,password)) == NULL){
								printf("Server: username or password wrong\n");
								TCP_send_enc_msg(ptr_cl->sharedkey,EVP_MAX_KEY_LENGTH, ptr_cl->id_sck, "!ER01", strlen("!ER01"));
								TCP_close(ptr_cl->id_sck);
								printf("Server: Connection %s:%d closed on socket %d\n", inet_ntoa(ptr_cl->addr.sin_addr),ntohs(ptr_cl->addr.sin_port), ptr_cl->id_sck);
								FD_CLR(ptr_cl->id_sck, &master);
								if (remove_client(&list_client, ptr_cl->username) == -1)
									error("Error: client remotion failed");
								free(datareceived);
								free(buffer);
								free(ciphertext);
								free(tmp);
								free(cipherkey);
								break;
							}
							printf("Server: password OK for %s\n",ptr_cl->username);
							//username & password ok
							resp_len = strlen("!OK01") + NONCELEN + EVP_MAX_KEY_LENGTH;
							resp_msg = (char*) malloc(resp_len);
							//response message (cmd|nonce)
							memcpy(resp_msg, "!OK01", strlen("!OK01"));
							memcpy(&resp_msg[strlen("!OK01")], ptr_cl->client_nonce, NONCELEN);
							memcpy(&resp_msg[strlen("!OK01")+NONCELEN], ptr_cl->sharedkey, EVP_MAX_KEY_LENGTH);
							TCP_send_enc_msg(ptr_cl->sharedkey,EVP_MAX_KEY_LENGTH, ptr_cl->id_sck, resp_msg, resp_len);
							//printf("[(M4) : S -> C]\n");
							printf("Server: %s authenticated\n",ptr_cl->username);
							free(datareceived);
							free(buffer);
							free(ciphertext);
							free(tmp);
							free(cipherkey);
							free(resp_msg);
							ptr_cl->protocol_step++;
							break;
						}
						default:
						{
							char* msg;
							int nb;
							fflush(stdout);
							msg = TCP_recv_enc_msg(ptr_cl->sharedkey,EVP_MAX_KEY_LENGTH,ptr_cl->id_sck,&nb);
							if(msg == NULL){
							  printf("Server: %s disconnected\n",ptr_cl->username);
							  TCP_close(ptr_cl->id_sck);
							  printf("Server: connection %s:%d closed on socket %d\n", inet_ntoa(ptr_cl->addr.sin_addr),ntohs(ptr_cl->addr.sin_port), ptr_cl->id_sck);
							  FD_CLR(ptr_cl->id_sck, &master);
							  if (remove_client(&list_client, ptr_cl->username) == -1)
								  error("Error: client remotion failed");
							  break;
							}
							printf("%s: %s\n",ptr_cl->username,msg);
							//check for commands
							char cmd[6];
							strncpy(cmd, msg, 5); //the first 5 characters are the command
							cmd[5] = '\0';
							if (strcmp(cmd, "!QUIT")==0) {
								printf("Server: %s disconnected\n",ptr_cl->username);
								TCP_close(ptr_cl->id_sck);
								printf("Server: connection %s:%d closed on socket %d\n", inet_ntoa(ptr_cl->addr.sin_addr),ntohs(ptr_cl->addr.sin_port), ptr_cl->id_sck);
								FD_CLR(ptr_cl->id_sck, &master);
								if (remove_client(&list_client, ptr_cl->username) == -1)
									error("Error: client remotion failed");
							} else if (strcmp(cmd, "!HELP") == 0) {
								TCP_send_enc_msg(ptr_cl->sharedkey,EVP_MAX_KEY_LENGTH,ptr_cl->id_sck,HELP,strlen(HELP));
							} else if (strcmp(cmd, "!LIST") == 0) {
								char* list;
								list = ls(server_dir);
								TCP_send_enc_msg(ptr_cl->sharedkey,EVP_MAX_KEY_LENGTH,ptr_cl->id_sck,list,strlen(list));
								free(list);
							} else if (strcmp(cmd, "!UPLD") == 0) {
								printf("Server: upload request from %s\n",ptr_cl->username);
								char *cmd_arg,*filename,*path;
								int i;
								char a;
								cmd_arg = str_trim(&msg[5]); //deletes white spaces
								a = cmd_arg[strlen(cmd_arg)-1];
								for (i = strlen(cmd_arg)-1; a != '/' &&  i!=0; i--)
									a = cmd_arg[i];
								filename = &cmd_arg[i+2]; //obtains file name, avoiding the path part
								path = malloc(strlen(filename)+strlen(server_dir));
								strcpy(path, server_dir);
								strcat(path, filename);
								TCP_send_enc_msg(ptr_cl->sharedkey,EVP_MAX_KEY_LENGTH, ptr_cl->id_sck, "!UPLD", strlen("!UPLD"));
								receive_enc_file(path, ptr_cl->sharedkey,EVP_MAX_KEY_LENGTH,ptr_cl->id_sck);
								printf("Server: file (%s) received from %s\n",filename,ptr_cl->username);
								free(path);
								free(cmd_arg);
								
							} else if (strcmp(cmd, "!DWLD") == 0) {
								printf("Server: download request from %s\n",ptr_cl->username);
								char* cmd_arg; //the rest of the message
								cmd_arg = str_trim(&msg[5]);
								if (!find_file(server_dir, cmd_arg)){
									TCP_send_enc_msg(ptr_cl->sharedkey,EVP_MAX_KEY_LENGTH, ptr_cl->id_sck, "Requested file not present", strlen("Requested file not present"));
									printf("Server: file not found\n");
								}
								else{
									char* fpath;
									TCP_send_enc_msg(ptr_cl->sharedkey,EVP_MAX_KEY_LENGTH, ptr_cl->id_sck, "!DWLD", strlen("!DWLD"));
									fpath = (char*)malloc(strlen(server_dir)+strlen(cmd_arg));
									strcpy(fpath,server_dir);
									strcat(fpath,cmd_arg);
									send_enc_file(fpath, ptr_cl->sharedkey,EVP_MAX_KEY_LENGTH,ptr_cl->id_sck);
									free(fpath);
									printf("Server: file sent to %s\n",ptr_cl->username);
								}
								free(cmd_arg);
							}
							free(msg);
							break;
					  }
					}
				}
			}
		}
	}
    return 0;
}

